package ro.sergiu.finder.models.property;

import ro.sergiu.finder.models.frame.FrameModel;
import java.awt.Color;
import java.awt.Dimension;
import java.beans.PropertyEditorSupport;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

public abstract class PropertyEditorGenerator extends PropertyEditorSupport {

    private class FieldMapp {

        private String name;
        private Class<? extends Object> type;
        private Method getter;
        private boolean isChanged;
        private Object defaultValue;
        private Object value;

        @Override
        public String toString() {
            return "{" + "name=" + name + ", type=" + type + '}';
        }

    }

    public static final String CUSTOM_VALUE = "<custom value>";
    public static final String DEFAULT = "<default>";
    public static final String NONE = "<none>";
    private static final String NEW = "new ";
    private List<FieldMapp> fields;
    private Class<? extends Object> clazz;
    private Object bean;

    @Override
    public String getJavaInitializationString() {
        fields = new ArrayList<FieldMapp>();
        bean = getValue();
        if (bean == null) {
            return "null";
        }
        try {
            clazz = bean.getClass();
            processBean();
        } catch (InstantiationException ex) {
            throw new RuntimeException("Cannot create a new instance!", ex);
        } catch (IllegalAccessException ex) {
            throw new RuntimeException("Cannot create a new instance!", ex);
        }
        return buildString();
    }

//<editor-fold defaultstate="collapsed" desc="Drawing code">
    private String buildString() {
        StringBuilder result = new StringBuilder();
        StringBuilder fieldsAsText = new StringBuilder();
        result.append(NEW).append(clazz.getName()).append("()");
        for (FieldMapp field : fields) {
            if (!field.isChanged || field.value == null) {
                continue;
            }
            fieldsAsText.append(field.name).append(" = ").append(writeValue(field)).append(";\n");
        }
        if (!fieldsAsText.toString().trim().isEmpty()) {
            result.append(String.format("{\n{\n%s}\n}\n", fieldsAsText, fields.size()));
        }
        return result.toString();
    }

    private String writeValue(FieldMapp field) {
        StringBuilder result = new StringBuilder();
        if (field.type.equals(String.class)) {
            result.append("\"").append(field.value).append("\"");
        } else if (field.type.equals(int.class) || field.type.equals(Integer.class)) {
            result.append(field.value);
        } else if (field.type.equals(float.class) || field.type.equals(Float.class)) {
            result.append(field.value).append("f");
        } else if (field.type.equals(double.class) || field.type.equals(Double.class)) {
            result.append(field.value).append("d");
        } else if (field.type.equals(long.class) || field.type.equals(Long.class)) {
            result.append(field.value).append("l");
        } else if (field.type.equals(short.class) || field.type.equals(Short.class)) {
            result.append(field.value);
        } else if (field.type.equals(boolean.class) || field.type.equals(Boolean.class)) {
            result.append(field.value);
        } else if (field.type.isEnum()) {
            result.append(field.value.getClass().getName()).append(".").append(field.value);
        } else if (field.type.equals(Color.class)) {
            int rgb = ((Color) field.value).getRGB();
            result.append(NEW).append(Color.class.getName()).append("(").append(rgb).append(")");
        } else if (field.type.equals(Dimension.class)) {
            Dimension dim = ((Dimension) field.value);
            result.append(NEW).append(Dimension.class.getName()).append("(").append(dim.width).append(",").append(dim.height).append(")");
        } else {
            throw new RuntimeException("I don't know how to build a " + field.type.getName());
        }
        return result.toString();
    }

    private void processBean() throws InstantiationException, IllegalAccessException {
        Field[] fieldList = clazz.getDeclaredFields();
        Object defInstance = clazz.newInstance();

        for (Field field : fieldList) {
            FieldMapp fieldMap = new FieldMapp();
            try {
                fieldMap.name = field.getName();
                fieldMap.type = field.getType();
                fieldMap.getter = findGetter(field);
                fieldMap.defaultValue = fieldMap.getter.invoke(defInstance);
                fieldMap.value = fieldMap.getter.invoke(bean);
                fieldMap.isChanged = fieldMap.value != fieldMap.defaultValue && (fieldMap.defaultValue == null || !fieldMap.value.equals(fieldMap.defaultValue));
                fields.add(fieldMap);
            } catch (IllegalArgumentException ex) {
                Logger.getLogger(PropertyEditorGenerator.class.getName()).log(Level.SEVERE, null, ex);
            } catch (InvocationTargetException ex) {
                Logger.getLogger(PropertyEditorGenerator.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

    private Method findGetter(Field field) {
        String methodName = field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1, field.getName().length());
        String[] prefixes = new String[]{"get", "is", "has"};
        for (String prefix : prefixes) {
            try {
                String getterName = prefix + methodName;
                Method method = clazz.getMethod(getterName, new Class<?>[]{});
                if (method != null && method.getModifiers() == Modifier.PUBLIC) {
                    if (method.getReturnType().equals(field.getType())) {
                        return method;
                    }
                }
            } catch (NoSuchMethodException ex) {
            } catch (SecurityException ex) {
            }
        }
        return null;
    }
//</editor-fold>

    /**
     * @return should return an instance with default vales.<br/>
     * First time when the user is opening the settings should be populated with
     * default values
     */
    public abstract Object getDefaultValue();

    @Override
    public String[] getTags() {
        return new String[]{DEFAULT, NONE};
    }

    @Override
    public boolean supportsCustomEditor() {
        return true;
    }

    @Override
    public String getAsText() {
        Object value = getValue();
        Object newObject = null;
        if (value != null) {
            newObject = getDefaultValue();
        }
        if (newObject != null && newObject.equals(value)) {
            return DEFAULT;
        }
        return CUSTOM_VALUE;
    }

    @Override
    public void setAsText(String text) {
        if (DEFAULT.equalsIgnoreCase(text)) {
            this.setValue(new FrameModel());
        } else if (NONE.equalsIgnoreCase(text)) {
            this.setValue(null);
        }
    }
}
